package top.xzxsrq.dynamicObject;

import javassist.*;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * class的NotFoundException问题
 * <p>
 * NotFoundException包括找不到类定义、找不到方法定义等等，我们这里主要讨论找不到类定义的情况。你可能会觉得奇怪，前面不是有这么多ClassPath实现，难道还有这些ClassPath没有覆盖的情况？ 是的，确实存在这种状态。比如我们使用javassist生成了一个自定义的类C， 由于该类完全是在内存中生成的，你无法通过一个具体的路径找到它，因此如果你后续希望再引用C，你可能会找不到它。为什么是可能？ javassist在加载类时会将其信息缓存起来，然而有的应用因为内存方面的考虑，会通过detach移除缓存信息。对于普通的类来说，缓存移除后通过添加LoaderClassPath或者其他ClassPath的方式可以重新加载，但是对于javassist动态生成的类来说，由于其只在内存中存在，因此无法再次找到其信息。 知道了问题以后，我们可以怎么处理呢？
 * <p>
 * a) 在CtClass.detach()之前，将生成的字节码保存到指定目录下:CtClass.writeFile(dir)， 然后通过指定DirClassPath来重新加载信息。
 * <p>
 * b) 如果CtClass操作已经被封装，无法加入writeFile方法的话，可以在系统启动时指定静态变量CtClass.debugDump="/home/admin/code_cache/dump"（早期的版本中可能没有这个变量）; 然后在需要对动态类进行二次代理时调用：
 */


/**
 * 所有用到的核心处理类class
 * <p>
 * * 自己的解题思路:
 * * 1. 最后使用类的使用自己调用这个方法返回
 * * 2. 把动态类的描述放在内存中, 让java自己回收不用到的引用
 * *
 * * 为什么这样用:
 * * 1. 写在磁盘中第二次加载同样需要引用一下
 * * 2. 如过是多线程的时候还要考虑隔开, 一样需要自己的去做文件引用
 *
 * @program: MyUtils
 * @create: 2021-09-17 18:55
 **/
@Data
public class AllUseCoreClassZX {
    private CtClass object; // 最后对象生成用到的
    private ClassFile classFile;
    private ConstPool constPool;
    private ClassPool pool;

    /**
     * @param writeDir 如果为空则不生成class文件
     * @return
     * @throws CannotCompileException
     */
    public Class<EmptyClassZX> getCClass(String writeDir) throws CannotCompileException, IOException, NotFoundException {
        CtField[] declaredFields = this.getObject().getDeclaredFields();
        List<String> collect = Arrays.stream(declaredFields)
                .map(CtField::getName)
                .collect(Collectors.toList());
        // 生成toString方法
        DynamicUtilsZX.makeToString(this, collect);
        Class<?> aClass = this.getObject().toClass(new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                return super.loadClass(name);
            }
        }, null);
        if (StringUtils.isNotEmpty(writeDir)) {
            this.getObject().writeFile(writeDir);
        }
        this.delObject();
        return (Class<EmptyClassZX>) aClass;
    }

    /**
     * 重复对象合并的时候记得要清除原来的在容器里面的对象,不然对象无法重新加载
     */
    public void delObject() {
        if (this.getObject().isFrozen()) {
            this.getObject().defrost();
        }
        this.getObject().detach();
    }

    public Class<EmptyClassZX> getCClass() throws CannotCompileException, IOException, NotFoundException {
        return getCClass(null);
    }

    /**
     * @return
     * @throws Exception
     */
    public static AllUseCoreClassZX getEmptyClass() throws Exception {
        ClassPool pool = new ClassPool(true); // 不在使用单利模式
        // 创建一个空类
        CtClass object = pool.makeClass("ZhiXinEmptyClass");
        return getEmptyClassCom(pool, object);
    }

    private static AllUseCoreClassZX getEmptyClassCom(ClassPool pool, CtClass object) throws NotFoundException, CannotCompileException {
        AllUseCoreClassZX result = new AllUseCoreClassZX();
        CtClass[] interfaces = object.getInterfaces();
        String name = Serializable.class.getName();
        String name1 = EmptyClassZX.class.getName();
        boolean b = Arrays.stream(interfaces).anyMatch(i -> i.getName().equals(name));
        pool.appendClassPath(name);
        pool.appendClassPath(name1);
        if (!b) {
            object.addInterface(pool.get(name));
        }
        b = Arrays.stream(interfaces).anyMatch(i -> i.getName().equals(name1));
        if (!b) {
            object.addInterface(pool.get(name1));
        }
        SerialVersionUID.setSerialVersionUID(object);
        ClassFile classFile = object.getClassFile();
        ConstPool constpool = classFile.getConstPool();
        result.setObject(object);
        result.setClassFile(classFile);
        result.setConstPool(constpool);
        result.setPool(pool);
        return result;
    }

    /**
     * 修改已经有的
     *
     * @param clazz
     * @return
     * @throws Exception
     */
    public static AllUseCoreClassZX getEmptyClass(Class<?> clazz) throws Exception {
        ClassPool pool = new ClassPool(true); // 不在使用单利模式
        // 获取已经有的类进行修改
        pool.insertClassPath(new ClassClassPath(clazz));
        CtClass object = pool.makeClass(pool.get(clazz.getName()).getClassFile());
        return getEmptyClassCom(pool, object);
    }

    private AllUseCoreClassZX() {
    }
}
